﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace DynamicMVVMSL
{
    public static class DynamicObjectCreator
    {
        private static AssemblyBuilder _assemblyBuilder = null;
        
        private static ModuleBuilder _moduleBuilder = null;

        private static readonly Type BaseClass = typeof (DynamicModelBase);

        public static string AssemblyName = "DynamicObjectCreator";

        public static DynamicModelBase CreateDynamicModel(object model)
        {
            if(model == null)
            {
                return null;
            }
            
            var modeType = model.GetType();

            var typeProperties = modeType.GetProperties().ToList();

            var dynamicType = CreateType(modeType, typeProperties);
            
            var returnObject = Activator.CreateInstance(dynamicType) as DynamicModelBase;

            returnObject.SetModel(model);

            returnObject.TypeProperties = typeProperties;

            return returnObject;
        }

        public static Type CreateType(Type type, List<PropertyInfo> typeProperties)
        {
            GenerateAssemblyAndModule();

            var typeBuilder = CreateType(_moduleBuilder, string.Format("{0}.{1}", AssemblyName, type.FullName));

            foreach (var propertyInfo in typeProperties)
            {
                //CreateProperty(typeBuilder, propertyInfo.Name, propertyInfo.PropertyType);
                CreateWraperProperty(typeBuilder, propertyInfo.Name, propertyInfo.PropertyType, type);
            }
            
            return typeBuilder.CreateType();
        }


        private static void GenerateAssemblyAndModule()
        {
            if (_assemblyBuilder != null) return;

            var assemblyName = new AssemblyName { Name = AssemblyName };

            var thisDomain = Thread.GetDomain();

            _assemblyBuilder = thisDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

            _moduleBuilder = _assemblyBuilder.DefineDynamicModule(AssemblyName, false);
        }

        private static TypeBuilder CreateType(ModuleBuilder modBuilder, string typeName)
        {
            var typeBuilder = modBuilder
                .DefineType(typeName,
                            TypeAttributes.Public |
                            TypeAttributes.Class |
                            TypeAttributes.AutoClass |
                            TypeAttributes.AnsiClass |
                            TypeAttributes.BeforeFieldInit |
                            TypeAttributes.AutoLayout,
                            typeof(DynamicModelBase));

            //new Type[] { typeof(IMyInterface) });

            return typeBuilder;
        }

        private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            var fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            var propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);

            var getMethoddBldr = tb.DefineMethod("get_" + propertyName,
                                                 MethodAttributes.Public |
                                                 MethodAttributes.SpecialName |
                                                 MethodAttributes.HideBySig,
                                                 propertyType, Type.EmptyTypes);

            var getIL = getMethoddBldr.GetILGenerator();
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldfld, fieldBuilder);
            getIL.Emit(OpCodes.Ret);

            var setMethoddBldr = tb.DefineMethod("set_" + propertyName,
                                                 MethodAttributes.Public |
                                                 MethodAttributes.SpecialName |
                                                 MethodAttributes.HideBySig,
                                                 null, new Type[] { propertyType });

            var setIL = setMethoddBldr.GetILGenerator();
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Stfld, fieldBuilder);
            setIL.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getMethoddBldr);
            propertyBuilder.SetSetMethod(setMethoddBldr);
        }

        private static void CreateWraperProperty(TypeBuilder tb, string propertyName, Type propertyType, Type objectType)
        {
            var propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);

            var getMethoddBldr = tb.DefineMethod("get_" + propertyName,
                                                 MethodAttributes.Public |
                                                 MethodAttributes.SpecialName |
                                                 MethodAttributes.HideBySig,
                                                 propertyType, Type.EmptyTypes);

            var getIL = getMethoddBldr.GetILGenerator();
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Call, BaseClass.GetMethod("get_Model"));
            getIL.Emit(OpCodes.Callvirt, objectType.GetMethod("get_" + propertyName));

            //getIL.Emit(OpCodes.Ldarg_0);
            //getIL.Emit(OpCodes.Ldstr, propertyName);
            //getIL.Emit(OpCodes.Call, baseClass.GetMethod("GetPropertyValue"));
            getIL.Emit(OpCodes.Ret);

            var setMethoddBldr = tb.DefineMethod("set_" + propertyName,
                                                 MethodAttributes.Public |
                                                 MethodAttributes.SpecialName |
                                                 MethodAttributes.HideBySig,
                                                 null, new Type[] { propertyType });

            var setIL = setMethoddBldr.GetILGenerator();
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldstr, propertyName);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Call, BaseClass.GetMethod("SetPropertyValue"));
            setIL.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getMethoddBldr);
            propertyBuilder.SetSetMethod(setMethoddBldr);
        }

        private static void CreateProperty()
        {
            //http://stackoverflow.com/questions/268778/whats-the-msil-to-call-a-base-classs-event-handler
            //http://blogs.msdn.com/b/joelpob/archive/2004/04/01/105862.aspx

            //MethodInfo baseMethod = typeof(EventConsumer).GetMethod("OnEventConsumed");
            //MethodBuilder methodBuilder = typeBuilder.DefineMethod("OnEventConsumed",
            //                                                       baseMethod.Attributes,
            //                                                       baseMethod.CallingConvention,
            //                                                       typeof(void),
            //                                                       new Type[] {typeof(object), 
            //                                                       typeof(EventArgs)});

            //ILGenerator ilGenerator = methodBuilder.GetILGenerator();

            //// load the first two args onto the stack 
            //ilGenerator.Emit(OpCodes.Ldarg_1);
            //ilGenerator.Emit(OpCodes.Ldarg_2);

            //// call the base method 
            //ilGenerator.EmitCall(OpCodes.Callvirt, baseMethod, new Type[0]);

            //// return 
            //ilGenerator.Emit(OpCodes.Ret); 
            //--------------------------------------

            //// load 'this' and the first two args onto the stack 
            //ilGenerator.Emit(OpCodes.Ldarg_0);
            //ilGenerator.Emit(OpCodes.Ldarg_1);
            //ilGenerator.Emit(OpCodes.Ldarg_2);

            //// call the base method 
            //ilGenerator.EmitCall(OpCodes.Call, baseMethod, new Type[0]);

            //// return 
            //ilGenerator.Emit(OpCodes.Ret); 
        }

    }
}